/**
 * 
 */
package org.snova.framework.proxy.spac.filter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;

import org.arch.config.IniProperties;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snova.framework.config.SnovaConfiguration;
import org.snova.framework.proxy.spac.filter.gfwlist.GFWListRule;
import org.snova.framework.proxy.spac.filter.gfwlist.HostUrlWildcardRule;
import org.snova.framework.proxy.spac.filter.gfwlist.UrlRegexRule;
import org.snova.framework.proxy.spac.filter.gfwlist.UrlWildcardRule;
import org.snova.framework.util.FileManager;

/**
 * @author yinqiwen
 * 
 */
public class GFWList extends SpacFilter
{
	protected static Logger	       logger	  = LoggerFactory
	                                                  .getLogger(GFWList.class);
	private static GFWList	       instance	  = new GFWList();
	
	private ArrayList<GFWListRule>	whiteList	= new ArrayList<GFWListRule>();
	private ArrayList<GFWListRule>	blackList	= new ArrayList<GFWListRule>();
	
	private GFWList()
	{
	}
	
	static GFWList getInstacne()
	{
		return instance;
	}
	
	@Override
	public boolean filter(HttpRequest req)
	{
		for (GFWListRule rule : whiteList)
		{
			if (rule.match(req))
			{
				return false;
			}
		}
		for (GFWListRule rule : blackList)
		{
			if (rule.match(req))
			{
				return true;
			}
		}
		return whiteList.isEmpty() && blackList.isEmpty();
	}
	
	public static void loadRules(String rules) throws IOException
	{
		ArrayList<GFWListRule> w = new ArrayList<GFWListRule>();
		ArrayList<GFWListRule> b = new ArrayList<GFWListRule>();
		BufferedReader reader = new BufferedReader(new StringReader(rules));
		int i = 0;
		while (true)
		{
			String line = reader.readLine();
			i++;
			if (i == 1)
			{
				continue;
			}
			if (null == line)
			{
				break;
			}
			line = line.trim();
			if(line.length() == 0)
			{
				continue;
			}
			if (line.startsWith("!"))
			{
				continue;
			}
			if (line.startsWith("@@||"))
			{
				HostUrlWildcardRule rule = new HostUrlWildcardRule();
				rule.init(line.substring(4));
				w.add(rule);
			}
			else if (line.startsWith("||"))
			{
				HostUrlWildcardRule rule = new HostUrlWildcardRule();
				rule.init(line.substring(2));
				b.add(rule);
			}
			else if (line.startsWith("|http"))
			{
				UrlWildcardRule rule = new UrlWildcardRule();
				rule.init(line.substring(1));
				b.add(rule);
			}
			else if (line.startsWith("/") && line.endsWith("/"))
			{
				UrlRegexRule rule = new UrlRegexRule();
				rule.init(line.substring(1, line.length() - 1));
				b.add(rule);
			}
			else
			{
				HostUrlWildcardRule rule = new HostUrlWildcardRule();
				rule.onlyHttp = true;
				rule.init(line);
				b.add(rule);
			}
		}
		instance.blackList = b;
		instance.whiteList = w;
	}
	
	public static void generatePAC(String url, String content)
	        throws IOException
	{
		String template = "/*\r\n"
		        + "* Proxy Auto-Config file generated by autoproxy2pac\r\n"
		        + "*  Rule source: ${RuleListUrl}\r\n"
		        + "*  Last update: ${RuleListDate}\r\n" + "*/\r\n"
		        + "function FindProxyForURL(url, host) {\r\n"
		        + "	var ${ProxyVar} = \"${ProxyString}\";\r\n"
		        + "	var ${DefaultVar} = \"${DefaultString}\";\r\n"
		        + "	${CustomCodePre}\r\n" + "   ${RulesBegin}\r\n"
		        + "	${RuleListCode}\r\n" + "   ${RulesEnd}\r\n"
		        + "	${CustomCodePost}\r\n" + "	return ${DefaultVar};\r\n" + "}";
		
		IniProperties cfg = SnovaConfiguration.getInstance().getIniProperties();
		String rulesBegin = "//-- AUTO-GENERATED RULES, DO NOT MODIFY!";
		String rulesEnd = "//-- END OF AUTO-GENERATED RULES";
		String proxyVar = "PROXY";
		String ruleListUrl = url;
		String ruleListDate = "";
		String proxyString = "PROXY " + cfg.getProperty("SPAC", "PACProxy");
		String defaultVar = "DEFAULT";
		String defaultString = "DIRECT";
		
		StringBuilder jsCode = new StringBuilder();
		
		BufferedReader reader = new BufferedReader(new StringReader(content));
		int i = 0;
		while (true)
		{
			String line = reader.readLine();
			i++;
			
			if (i == 1)
			{
				continue;
			}
			if (null == line)
			{
				break;
			}
			line = line.trim();
			// comment
			if (line.length() == 0 || line.startsWith("!"))
			{
				continue;
			}
			String proxy_var = "PROXY";
			if (line.startsWith("@@"))
			{
				line = line.substring(2);
				proxy_var = "DEFAULT";
			}
			String jsRegexp = "";
			if (line.startsWith("/") && line.endsWith("/"))
			{
				jsRegexp = line.substring(1, line.length() - 1);
			}
			else
			{
				jsRegexp = line.replaceAll("\\*+", "*");
				
				jsRegexp = jsRegexp.replaceFirst("\\^\\|$", "^");
				jsRegexp = jsRegexp.replaceAll("(\\W)", "\\\\$1");
				
				jsRegexp = jsRegexp.replaceAll("\\\\\\*", ".*");
				jsRegexp = jsRegexp.replaceAll("\\\\^",
				        "(?:[^\\w\\-.%\u0080-\uFFFF]|$)");
				
				jsRegexp = jsRegexp.replaceFirst("^\\\\\\|\\\\\\|",
				        "^[\\\\w\\\\-]+:\\\\/+(?!\\\\/)(?:[^\\\\/]+\\\\.)?");
				
				jsRegexp = jsRegexp.replaceFirst("^\\\\\\|", "^");
				jsRegexp = jsRegexp.replaceFirst("\\\\\\|$", "$");
				
				jsRegexp = jsRegexp.replaceFirst("^(\\.\\*)", "");
				jsRegexp = jsRegexp.replaceFirst("(\\.\\*)$", "");
			}
			if (jsRegexp.length() == 0)
			{
				logger.error("There is one rule that matches all URL, which is highly *NOT* recommended: "
				        + line);
			}
			String jsLine = String.format("if(/%s/i.test(url)) return %s;",
			        jsRegexp, proxy_var);
			if (proxy_var.equals("DEFAULT"))
			{
				StringBuilder tmp = new StringBuilder();
				tmp.append(jsLine).append("\r\n\t");
				tmp.append(jsCode);
				jsCode = tmp;
			}
			else
			{
				jsCode.append(jsLine).append("\r\n\t");
			}
		}
		
		try
		{
			template = template.replace("${RulesBegin}", rulesBegin);
			template = template.replace("${RulesEnd}", rulesEnd);			
			template = template.replace("${RuleListUrl}", ruleListUrl);
			template = template.replace("${RuleListDate}", ruleListDate);
			template = template.replace("${ProxyVar}", proxyVar);
			template = template.replace("${ProxyString}", proxyString);
			template = template.replace("${DefaultVar}", defaultVar);
			template = template.replace("${DefaultString}", defaultString);
			template = template.replace("${CustomCodePre}", "");
			template = template.replace("${RuleListCode}", jsCode.toString());
			template = template.replace("${CustomCodePost}", "");
		}
		catch (Exception e)
		{
			logger.error("Failed to generate PAC.", e);
		}
		FileManager.writeFile(template, "spac/snova-gfwlist.pac");
	}
}
